//go:build windows
// +build windows

package win系统类

import (
	"runtime"
	"syscall"
	"time"
	"unsafe"
)

const (
	cfUnicodetext = 13
	gmemMoveable  = 0x0002
)

// X取剪辑板文本 从剪贴板中获取文本内容。
// 来源 https://github.com/atotto/clipboard
func X取剪辑板文本() (string, error) {
	// LockOSThread确保整个方法从头到尾都在同一个线程上执行（它实际上锁定了goroutine线程的属性）。
	// 否则，如果goroutine在执行过程中切换线程（这是一种常见的做法），OpenClipboard和CloseClipboard将在两个不同的线程上发生，这将导致剪贴板死锁。
	runtime.LockOSThread()
	defer runtime.UnlockOSThread()

	// 检查剪贴板上是否有Unicode文本格式的数据可用。
	if formatAvailable, _, err := isClipboardFormatAvailable.X调用(cfUnicodetext); formatAvailable == 0 {
		return "", err
	}

	// 等待剪贴板准备好，避免在剪贴板未准备好时尝试打开它。
	err := waitOpenClipboard()
	if err != nil {
		return "", err
	}

	// 尝试获取剪贴板上的Unicode文本数据。
	h, _, err := getClipboardData.X调用(cfUnicodetext)
	if h == 0 {
		// 如果获取数据失败，则尝试关闭剪贴板并返回错误。
		_, _, _ = closeClipboard.X调用()
		return "", err
	}

	// 将剪贴板数据锁定到内存中，以便可以访问。
	l, _, err := globalLock.X调用(h)
	if l == 0 {
		// 如果锁定失败，则尝试关闭剪贴板并返回错误。
		_, _, _ = closeClipboard.X调用()
		return "", err
	}

	// 将锁定的内存中的Unicode数据转换为UTF-8字符串。
	text := syscall.UTF16ToString((*[1 << 20]uint16)(unsafe.Pointer(l))[:])

	// 解锁剪贴板数据，允许其他进程或线程访问。
	r, _, err := globalUnlock.X调用(h)
	if r == 0 {
		// 如果解锁失败，则尝试关闭剪贴板并返回错误。
		_, _, _ = closeClipboard.X调用()
		return "", err
	}

	// 关闭剪贴板，释放所有者线程对剪贴板的占用。
	closed, _, err := closeClipboard.X调用()
	if closed == 0 {
		return "", err
	}
	return text, nil
}

// X置剪辑板文本 将指定的文本设置到剪贴板中。
// 参数：
// 文本 - 要设置到剪贴板的文本内容。
// 返回值：
// error - 如果设置剪贴板文本时发生错误，则返回相应的错误。
// 来源 https://github.com/atotto/clipboard
func X置剪辑板文本(文本 string) error {
	// LockOSThread确保整个方法从头到尾都在同一个线程上执行（它实际上锁定了goroutine线程的属性）。
	// 否则，如果goroutine在执行过程中切换线程（这是一种常见的做法），OpenClipboard和CloseClipboard将在两个不同的线程上发生，这将导致剪贴板死锁。
	runtime.LockOSThread()
	defer runtime.UnlockOSThread()

	// waitOpenClipboard用于确保OpenClipboard在同一个线程中调用。
	err := waitOpenClipboard()
	if err != nil {
		return err
	}

	// emptyClipboard.X调用清空剪贴板内容，为新文本做准备。
	r, _, err := emptyClipboard.X调用(0)
	if r == 0 {
		// 失败时调用closeClipboard释放资源
		_, _, _ = closeClipboard.X调用()
		return err
	}

	// 将文本转换为UTF16格式，因为剪贴板处理的是宽字符。
	data := syscall.StringToUTF16(文本)

	// globalAlloc分配一段可移动的全局内存，用于存放剪贴板数据。
	h, _, err := globalAlloc.X调用(gmemMoveable, uintptr(len(data)*int(unsafe.Sizeof(data[0]))))
	if h == 0 {
		// 分配失败时尝试关闭并清理
		_, _, _ = closeClipboard.X调用()
		return err
	}
	defer func() {
		// 无论如何，最终释放全局内存
		if h != 0 {
			globalFree.X调用(h)
		}
	}()

	// globalLock将全局内存块锁定到物理内存中，以便对其进行写入。
	l, _, err := globalLock.X调用(h)
	if l == 0 {
		// 锁定失败时尝试关闭并清理
		_, _, _ = closeClipboard.X调用()
		return err
	}

	// lstrcpyW将源字符串复制到目标内存中。
	r, _, err = lstrcpyW.X调用(l, uintptr(unsafe.Pointer(&data[0])))
	if r == 0 {
		// 复制失败时尝试关闭并清理
		_, _, _ = closeClipboard.X调用()
		return err
	}

	// globalUnlock解锁之前锁定的全局内存块。
	r, _, err = globalUnlock.X调用(h)
	if r == 0 {
		// 解锁失败但不返回错误，因为这不直接导致操作失败。
		if err.(syscall.Errno) != 0 {
			_, _, _ = closeClipboard.X调用()
			return err
		}
	}

	// setClipboardData设置剪贴板数据，使其可用作剪贴板内容。
	r, _, err = setClipboardData.X调用(cfUnicodetext, h)
	if r == 0 {
		// 设置失败时尝试关闭并清理
		_, _, _ = closeClipboard.X调用()
		return err
	}
	// 清除全局内存句柄，避免延迟清理
	h = 0

	// closeClipboard关闭剪贴板，释放相关资源。
	closed, _, err := closeClipboard.X调用()
	if closed == 0 {
		// 关闭失败返回错误
		return err
	}
	// 操作成功完成
	return nil
}

// waitOpenClipboard打开剪贴板，最多等待一秒钟。
func waitOpenClipboard() error {
	started := time.Now()
	limit := started.Add(time.Second)
	var r uintptr
	var err error
	for time.Now().Before(limit) {
		r, _, err = openClipboard.X调用(0)
		if r != 0 {
			return nil
		}
		time.Sleep(time.Millisecond)
	}
	return err
}
